// Copyright 2021-2024 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import { codegenInfo, DescService } from "@bufbuild/protobuf";
import type {
  GeneratedFile,
  RuntimeImports,
  Schema,
} from "@bufbuild/protoplugin/ecmascript";
import {
  getMessageType,
  MethodKind,
} from "./util.js";
import {localName} from "./runtime-import/names";
import {createRuntimeImports} from "./runtime-import/rumtime-imports";
import {Printable} from "@bufbuild/protoplugin/dist/esm/ecmascript/generated-file";

export const runtime = createRuntimeImports();


export function generateEts(schema: Schema) {
  for (const runtimeKey in schema.runtime) {
    schema.runtime[runtimeKey as keyof RuntimeImports] = {
      ...schema.runtime[runtimeKey as keyof RuntimeImports],
      from: "@bitfun/protobuf_ets",
    }
  }
  (codegenInfo as any).packageName = "@bitfun/protobuf_ets";

  for (const file of schema.files) {
    if (file.services.length > 0) {
      const f_connect = schema.generateFile(file.name + "_connect.ts");
      for (const service of file.services) {
        generateService(schema, f_connect, service);
      }
    }
  }
  const infoArr = (schema as any).getFileInfo();
  infoArr.forEach((info: { name: string; content: string }) => {
    info.name = info.name.slice(0, -3) + '.ets';
    info.content = info.content.split(/\r?\n/).map(line => {
      if (line.startsWith('import') && line.endsWith('_pb.js";')) {
        return line.replace('_pb.js', '_pb');
      }
      return line;
    }).join('\n');
  });
  (schema as any).getFileInfo = () => infoArr;
  (codegenInfo as any).packageName = "@bufbuild/protobuf";
}

function generateService(
  schema: Schema,
  f: GeneratedFile,
  service: DescService
){
  const {
    Transport,
    CallOptions,
  } = runtime;
  f.print(f.exportDecl("class", localName(service)),"{");
  f.print("  readonly typeName: string = ", f.string(service.typeName), ";");
  f.print();
  f.print("  private transport: ", Transport, ";");
  f.print();
  f.print("  constructor(transport: ", Transport, ") {");
  f.print(`    this.transport = transport;`);
  f.print(`  }`);
  for (const method of service.methods){
    let input: Printable;
    let output: Printable;
    let doMethod: Printable;
    switch (method.methodKind as MethodKind ) {
      case MethodKind.Unary:
        input = method.input;
        output = ["Promise<", method.output, ">"];
        doMethod = "unary";
        break;
      case MethodKind.ServerStreaming:
        input = method.input;
        output = ["AsyncIterable<", method.output, ">"];
        doMethod = "serverStream";
        break;
      case MethodKind.ClientStreaming:
        input = ["AsyncIterable<", method.input, ">"];
        output = ["Promise<", method.output, ">"];
        doMethod = "clientStream";
        break;
      case MethodKind.BiDiStreaming:
        input = ["AsyncIterable<", method.input, ">"];
        output = ["AsyncIterable<", method.output, ">"];
        doMethod = "bitStream";
    }
    f.print();
    f.print("  ", localName(method), "(request: ", input, ", callOptions?: ", CallOptions, "): ", output, " {");
    f.print("    return this.transport.", doMethod, `(this.typeName, "`, localName(method), `", request, `, getMessageType(method.output), ".instance, callOptions);")
    f.print("  }");
  }
  f.print("};");
}